package main

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"net"
	"os"
	"os/signal"
	"sync"
	"syscall"
	"time"

	_ "github.com/go-sql-driver/mysql"
	"github.com/joho/godotenv"
)

var (
	rDB    *sql.DB   // 数据库
	realms realmList // 服务器列表
)

func main() {
	// 加载配置文件
	err := godotenv.Load("realmd.env")
	if err != nil {
		panic(fmt.Sprintf("加载配置文件出错: %v", err))
	}

	// 初始化日志
	initLog()

	// 获取数据库连接串
	dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s",
		os.Getenv("DB_USER"),
		os.Getenv("DB_PASSWORD"),
		os.Getenv("DB_ADDRESS"),
		os.Getenv("DB_NAME"))

	// 打开数据库连接
	rDB, err = sql.Open("mysql", dsn)
	if err != nil {
		panic(fmt.Sprintf("打开数据库失败：%v", err))
	}
	defer func(rDB *sql.DB) {
		logDB("关闭数据库连接")
		err := rDB.Close()
		if err != nil {
			logDBError("关闭数据库发生错误：%v", err)
		}
	}(rDB)

	// 解析网络地址
	addr, err := net.ResolveTCPAddr(
		os.Getenv("NET_NETWORK"),
		os.Getenv("NET_ADDRESS"))
	if err != nil {
		panic(fmt.Sprintf("解析网络地址出错：%v", err))
	}
	// 开始监听网络
	l, err := net.ListenTCP(
		os.Getenv("NET_NETWORK"), addr)
	if err != nil {
		panic(fmt.Sprintf("监听网络地址出错：%v", err))
	}
	defer func(l *net.TCPListener) {
		log("关闭网络监听器")
		err := l.Close()
		if err != nil {
			logError("关闭网络监听器发生错误：%v", err)
		}
	}(l)

	// 准备上下文和取消函数
	ctx, cancel := context.WithCancel(context.Background())

	// 准备等待组
	waitGroup := &sync.WaitGroup{}

	// 等待数据库退出
	waitGroup.Add(1)
	go pingDB(waitGroup, ctx)

	// 信号处理
	shutdownChan := make(chan os.Signal, 1)
	signal.Notify(shutdownChan, os.Interrupt, syscall.SIGTERM)
	signal.Notify(shutdownChan, os.Interrupt, syscall.SIGINT)

	// 更新服务器列表
	realms.init()
	realms.updateRealms(true)

	log("服务器已上线")

	for {
		select {
		case <-shutdownChan:
			goto exit
		default:
			// 设置接收超时
			err := l.SetDeadline(time.Now().Add(time.Second))
			if err != nil {
				logError("设置接收连接超时错误：%v", err)
				goto exit
			}

			// 接收连接
			rw, err := l.Accept()
			if err != nil {
				var opErr *net.OpError
				if errors.As(err, &opErr) && opErr.Timeout() {
					continue
				}
				logError("接收连接发生错误: %v", err)
				goto exit
			}

			// 启动连接处理
			waitGroup.Add(1)
			c := &conn{rw: rw}
			go c.run(waitGroup, ctx)
		}
	}

exit:
	log("服务器正在下线")

	// 取消上下文
	cancel()
	// 等待退出
	waitGroup.Wait()
}

// pingDB 保持数据库连接
func pingDB(waitGroup *sync.WaitGroup, context context.Context) {
	defer waitGroup.Done()

	for {
		select {
		case <-context.Done():
			return
		default:
			err := rDB.Ping()
			if err != nil {
				logDBError("数据库连接发生错误：%v", err)
			}
			time.Sleep(time.Second * 5)
		}
	}
}
